package net.devkit.common.dao.impl;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.annotation.Resource;
import javax.persistence.Id;

import net.devkit.common.dao.GenericDao;
import net.devkit.common.toolkit.Pager;
import net.devkit.common.toolkit.StringUtils;
import net.devkit.common.toolkit.reflection.GenericsUtils;

import org.hibernate.Query;
import org.hibernate.SQLQuery;
import org.hibernate.Session;
import org.hibernate.SessionFactory;

/**
 * 公共的数据访问接口实现
 * 
 * @param <T>
 * @param <ID>
 */
@SuppressWarnings({ "unchecked" })
public abstract class GenericDaoImpl<T extends Serializable, ID extends Serializable>
		implements GenericDao<T, ID> {

	@Resource(name = "sessionFactory")
	private SessionFactory sessionFactory;

	private Session getSession() {
		return sessionFactory.getCurrentSession();
	}

	private final Class<T> entityClass; // T类的class
	private String idName = null; // T类的idName
	private final String LIST_ALL_JPQL; // 查询所有
	private final String COUNT_ALL_JPQL; // 查询总记录数

	public GenericDaoImpl() {
		super();
		this.entityClass = (Class<T>) GenericsUtils
				.getSuperClassGenricType(this.getClass());
		// 标注在getter方法上
		Method[] methods = this.entityClass.getDeclaredMethods();
		for (Method method : methods) {
			if (method.getName().startsWith("get")) {
				if (method.isAnnotationPresent(Id.class)) {
					String temp = method.getName().replaceAll("get", "");
					// 将第一个字母变成小写
					this.idName = StringUtils.firstLetterToLower(temp);
					break;
				}
			}
		}

		String entityClassName = this.entityClass.getSimpleName();
		// TODO @Entity name not null
		String lowerEntityClassName = StringUtils
				.firstLetterToLower(entityClassName);
		LIST_ALL_JPQL = "from " + entityClassName + " " + lowerEntityClassName;
		COUNT_ALL_JPQL = "select count(" + lowerEntityClassName + "." + idName
				+ ") from " + entityClassName + " " + lowerEntityClassName;

	}

	@Override
	public ID create(final T t) {
		return (ID) this.getSession().save(t);
	}

	@Override
	public void createBatch(final Collection<T> ts) {
		final Session session = this.getSession();
		int i = 1;
		for (T t : ts) {
			i++;
			session.save(t);
			if (i % 20 == 0)
				session.flush();
		}
	}

	@Override
	public void remove(final T t) {
		this.getSession().delete(t);
	}

	@Override
	public void removeById(final ID id) {
		this.getSession().delete(this.findEntityById(id));
	}

	@Override
	public Integer removeByJpql(final StringBuilder jpql, final Object... args) {
		Query query = this.getSession().createQuery(jpql.toString());
		this.setParameters(query, args);
		return query.executeUpdate();
	}

	@Override
	public void removeBatch(final Collection<ID> ids) {
		final Session session = this.getSession();
		int i = 1;
		for (ID id : ids) {
			i++;
			session.delete(this.findEntityById(id));
			if (i % 20 == 0)
				session.flush();
		}
	}

	@Override
	public void modify(final T t) {
		this.getSession().merge(t);
	}

	@Override
	public Integer modifyByJpql(final StringBuilder jpql, final Object... args) {
		Query query = this.getSession().createQuery(jpql.toString());
		this.setParameters(query, args);
		return query.executeUpdate();
	}

	@Override
	public void modifyBatch(final Collection<T> ts) {
		final Session session = this.getSession();
		int i = 1;
		for (T t : ts) {
			i++;
			session.update(t);
			if (i % 20 == 0)
				session.flush();
		}
	}

	@Override
	public void createOrModifyBatch(Collection<T> ts) {
		final Session session = this.getSession();
		int i = 1;
		for (T t : ts) {
			i++;
			session.saveOrUpdate(t);
			if (i % 20 == 0)
				session.flush();
		}
	}

	@Override
	public void createOrModify(final T t) {
		this.getSession().saveOrUpdate(t);
	}

	@Override
	public T loadEntityById(ID id) {
		return (T) this.getSession().load(entityClass, id);
	}

	@Override
	public T findEntityById(final ID id) {
		return (T) this.getSession().get(entityClass, id);
	}

	@Override
	public List<T> findEntitiesByJpql(final StringBuilder jpql,
			final Object... args) {
		Query query = this.getSession().createQuery(jpql.toString());
		this.setParameters(query, args);
		return query.list();
	}

	@Override
	public T findUniqueEntityByJpql(final StringBuilder jpql,
			final Object... args) {
		Query query = this.getSession().createQuery(jpql.toString());
		this.setParameters(query, args);
		return (T) query.setMaxResults(1).uniqueResult();
	}

	@Override
	public Pager<T> findPagerByJpql(final Integer pagesize,
			final Integer pageno, final StringBuilder jpql,
			final Object... args) {
		Query query = this.getSession().createQuery(jpql.toString());
		this.setParameters(query, args);

		StringBuilder countQueryStr = new StringBuilder(" select count (")
				.append(idName).append(")")
				.append(removeSelect(removeOrders(jpql.toString())));

		// 计算总记录数
		Long totalsize = this.findCountByJpql(countQueryStr, args);

		query.setFirstResult(pagesize * (pageno - 1));
		query.setMaxResults(pagesize);

		List<T> result = query.list();
		// 计算总页数
		Integer pagecount = (totalsize.intValue() + pagesize - 1) / pagesize;

		return new Pager<T>(pageno, pagesize, pagecount, totalsize, result);
	}

	/**
	 * 去除hql的select 子句， 未考虑union的情况,用于findPagerByJpql.
	 */
	private static String removeSelect(String hql) {
		int beginPos = hql.toLowerCase().indexOf("from");
		return hql.substring(beginPos);
	}

	/**
	 * 去除hql的orderby 子句，用于findPagerByJpql.
	 */
	private static String removeOrders(String hql) {
		Pattern p = Pattern.compile("order\\s*by[\\w|\\W|\\s|\\S]*",
				Pattern.CASE_INSENSITIVE);
		Matcher m = p.matcher(hql);
		StringBuffer sb = new StringBuffer();
		while (m.find()) {
			m.appendReplacement(sb, "");
		}
		m.appendTail(sb);
		return sb.toString();
	}

	@Override
	public List<T> findAll() {
		return this.getSession().createQuery(LIST_ALL_JPQL.toString()).list();
	}

	@Override
	public Long findCountAll() {
		return (Long) this.getSession().createQuery(COUNT_ALL_JPQL.toString())
				.uniqueResult();
	}

	@Override
	public Long findCountByJpql(final StringBuilder jpql, final Object... args) {
		Query query = this.getSession().createQuery(jpql.toString());
		this.setParameters(query, args);
		return (Long) query.uniqueResult();
	}

	@Override
	public List<?> CallProcedure(String procName) {
		final Session session = this.getSession();
		String proc = "{call " + procName + "}";
		SQLQuery query = session.createSQLQuery(proc);

		List<?> list = query.list();
		return list;
	}

	@Override
	public void flush() {
		this.getSession().flush();
	}

	@Override
	public void clear() {
		this.getSession().clear();
	}

	
	@Override
	public StringBuilder obtainJpqlOfBasic() {
		return new StringBuilder(this.LIST_ALL_JPQL);
	}

	/**
	 * 设置参数
	 * 
	 * @param query
	 * @param params
	 */
	private void setParameters(Query query, Object[] params) {
		int len = params.length;
		for (int i = 0; i < len; i++) {
			query.setParameter(i, params[i]);
		}
	}

}
